1   package org.apache.lucene.index;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.IdentityHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.lucene.codecs.DocValuesProducer;
31  import org.apache.lucene.store.Directory;
32  import org.apache.lucene.util.Accountable;
33  import org.apache.lucene.util.Accountables;
34  import org.apache.lucene.util.Bits;
35  import org.apache.lucene.util.RamUsageEstimator;
36  import org.apache.lucene.util.Version;
37  
38  /** Encapsulates multiple producers when there are docvalues updates as one producer */
39  // TODO: try to clean up close? no-op?
40  // TODO: add shared base class (also used by per-field-pf?) to allow "punching thru" to low level producer?
41  class SegmentDocValuesProducer extends DocValuesProducer {
42    
43    private static final long LONG_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Long.class);
44    private static final long BASE_RAM_BYTES_USED =
45        RamUsageEstimator.shallowSizeOfInstance(SegmentDocValuesProducer.class);
46  
47    final Map<String,DocValuesProducer> dvProducersByField = new HashMap<>();
48    final Set<DocValuesProducer> dvProducers = Collections.newSetFromMap(new IdentityHashMap<DocValuesProducer,Boolean>());
49    final List<Long> dvGens = new ArrayList<>();
50    
51    /**
52     * Creates a new producer that handles updated docvalues fields
53     * @param si commit point
54     * @param dir directory
55     * @param coreInfos fieldinfos for the segment
56     * @param allInfos all fieldinfos including updated ones
57     * @param segDocValues producer map
58     */
59    SegmentDocValuesProducer(SegmentCommitInfo si, Directory dir, FieldInfos coreInfos, FieldInfos allInfos, SegmentDocValues segDocValues) throws IOException {
60      boolean success = false;
61      try {
62        Version ver = si.info.getVersion();
63        if (ver != null && ver.onOrAfter(Version.LUCENE_4_9_0)) {
64          DocValuesProducer baseProducer = null;
65          for (FieldInfo fi : allInfos) {
66            if (fi.getDocValuesType() == DocValuesType.NONE) {
67              continue;
68            }
69            long docValuesGen = fi.getDocValuesGen();
70            if (docValuesGen == -1) {
71              if (baseProducer == null) {
72                // the base producer gets the original fieldinfos it wrote
73                baseProducer = segDocValues.getDocValuesProducer(docValuesGen, si, dir, coreInfos);
74                dvGens.add(docValuesGen);
75                dvProducers.add(baseProducer);
76              }
77              dvProducersByField.put(fi.name, baseProducer);
78            } else {
79              assert !dvGens.contains(docValuesGen);
80              // otherwise, producer sees only the one fieldinfo it wrote
81              final DocValuesProducer dvp = segDocValues.getDocValuesProducer(docValuesGen, si, dir, new FieldInfos(new FieldInfo[] { fi }));
82              dvGens.add(docValuesGen);
83              dvProducers.add(dvp);
84              dvProducersByField.put(fi.name, dvp);
85            }
86          }
87        } else {
88          // For pre-4.9 indexes, especially with doc-values updates, multiple
89          // FieldInfos could belong to the same dvGen. Therefore need to make sure
90          // we initialize each DocValuesProducer once per gen.
91          Map<Long,List<FieldInfo>> genInfos = new HashMap<>();
92          for (FieldInfo fi : allInfos) {
93            if (fi.getDocValuesType() == DocValuesType.NONE) {
94              continue;
95            }
96            List<FieldInfo> genFieldInfos = genInfos.get(fi.getDocValuesGen());
97            if (genFieldInfos == null) {
98              genFieldInfos = new ArrayList<>();
99              genInfos.put(fi.getDocValuesGen(), genFieldInfos);
100           }
101           genFieldInfos.add(fi);
102         }
103       
104         for (Map.Entry<Long,List<FieldInfo>> e : genInfos.entrySet()) {
105           long docValuesGen = e.getKey();
106           List<FieldInfo> infos = e.getValue();
107           final DocValuesProducer dvp;
108           if (docValuesGen == -1) {
109             // we need to send all FieldInfos to gen=-1, but later we need to
110             // record the DVP only for the "true" gen=-1 fields (not updated)
111             dvp = segDocValues.getDocValuesProducer(docValuesGen, si, dir, coreInfos);
112           } else {
113             dvp = segDocValues.getDocValuesProducer(docValuesGen, si, dir, new FieldInfos(infos.toArray(new FieldInfo[infos.size()])));
114           }
115           dvGens.add(docValuesGen);
116           dvProducers.add(dvp);
117           for (FieldInfo fi : infos) {
118             dvProducersByField.put(fi.name, dvp);
119           }
120         }
121       }
122       success = true;
123     } finally {
124       if (success == false) {
125         try {
126           segDocValues.decRef(dvGens);
127         } catch (Throwable t) {
128           // Ignore so we keep throwing first exception
129         }
130       }
131     }
132   }
133 
134   @Override
135   public NumericDocValues getNumeric(FieldInfo field) throws IOException {
136     DocValuesProducer dvProducer = dvProducersByField.get(field.name);
137     assert dvProducer != null;
138     return dvProducer.getNumeric(field);
139   }
140 
141   @Override
142   public BinaryDocValues getBinary(FieldInfo field) throws IOException {
143     DocValuesProducer dvProducer = dvProducersByField.get(field.name);
144     assert dvProducer != null;
145     return dvProducer.getBinary(field);
146   }
147 
148   @Override
149   public SortedDocValues getSorted(FieldInfo field) throws IOException {
150     DocValuesProducer dvProducer = dvProducersByField.get(field.name);
151     assert dvProducer != null;
152     return dvProducer.getSorted(field);
153   }
154 
155   @Override
156   public SortedNumericDocValues getSortedNumeric(FieldInfo field) throws IOException {
157     DocValuesProducer dvProducer = dvProducersByField.get(field.name);
158     assert dvProducer != null;
159     return dvProducer.getSortedNumeric(field);
160   }
161 
162   @Override
163   public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException {
164     DocValuesProducer dvProducer = dvProducersByField.get(field.name);
165     assert dvProducer != null;
166     return dvProducer.getSortedSet(field);
167   }
168 
169   @Override
170   public Bits getDocsWithField(FieldInfo field) throws IOException {
171     DocValuesProducer dvProducer = dvProducersByField.get(field.name);
172     assert dvProducer != null;
173     return dvProducer.getDocsWithField(field);
174   }
175 
176   @Override
177   public void checkIntegrity() throws IOException {
178     for (DocValuesProducer producer : dvProducers) {
179       producer.checkIntegrity();
180     }
181   }
182   
183   @Override
184   public void close() throws IOException {
185     throw new UnsupportedOperationException(); // there is separate ref tracking
186   }
187 
188   @Override
189   public long ramBytesUsed() {
190     long ramBytesUsed = BASE_RAM_BYTES_USED;
191     ramBytesUsed += dvGens.size() * LONG_RAM_BYTES_USED;
192     ramBytesUsed += dvProducers.size() * RamUsageEstimator.NUM_BYTES_OBJECT_REF;
193     ramBytesUsed += dvProducersByField.size() * 2 * RamUsageEstimator.NUM_BYTES_OBJECT_REF;
194     for (DocValuesProducer producer : dvProducers) {
195       ramBytesUsed += producer.ramBytesUsed();
196     }
197     return ramBytesUsed;
198   }
199 
200   @Override
201   public Collection<Accountable> getChildResources() {
202     final List<Accountable> resources = new ArrayList<>(dvProducers.size());
203     for (Accountable producer : dvProducers) {
204       resources.add(Accountables.namedAccountable("delegate", producer));
205     }
206     return Collections.unmodifiableList(resources);
207   }
208 
209   @Override
210   public String toString() {
211     return getClass().getSimpleName() + "(producers=" + dvProducers.size() + ")";
212   }
213 }